<!DOCTYPE html>
<html lang=zh>
<head>
  <meta charset="utf-8">
  
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, minimal-ui">
  <meta name="renderer" content="webkit">
  <meta http-equiv="Cache-Control" content="no-transform" />
  <meta http-equiv="Cache-Control" content="no-siteapp" />
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="black">
  <meta name="format-detection" content="telephone=no,email=no,adress=no">
  <!-- Color theme for statusbar -->
  <meta name="theme-color" content="#000000" />
  <!-- 强制页面在当前窗口以独立页面显示,防止别人在框架里调用页面 -->
  <meta http-equiv="window-target" content="_top" />
  <!-- 谷歌收录 -->
  <meta name="google-site-verification" content="-5hl8eC4wfapGZhZouwMVUcKG0iaynijVRJbqVlzXm4" />
  
  
  <title>Go语言中的通道 | 鴻塵</title>
  <meta name="description" content="摘要：看完这篇，Go语言的通道及Goroutine你就都会了…">
<meta property="og:type" content="article">
<meta property="og:title" content="Go语言中的通道">
<meta property="og:url" content="https://hwame.top/20220327/channel-and-goroutine-in-go.html">
<meta property="og:site_name" content="鴻塵">
<meta property="og:description" content="摘要：看完这篇，Go语言的通道及Goroutine你就都会了…">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://img.jbzj.com/file_images/article/202111/2021110914311155.png">
<meta property="og:image" content="https://img.jbzj.com/file_images/article/202111/2021110914311156.png">
<meta property="og:image" content="https://img.jbzj.com/file_images/article/202111/2021110914311257.png">
<meta property="og:image" content="https://img.jbzj.com/file_images/article/202111/2021110914311258.png">
<meta property="og:image" content="https://img.jbzj.com/file_images/article/202111/2021110914311259.png">
<meta property="article:published_time" content="2022-03-27T11:06:16.000Z">
<meta property="article:modified_time" content="2022-03-27T12:00:41.000Z">
<meta property="article:author" content="鴻塵">
<meta property="article:tag" content="Go">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://img.jbzj.com/file_images/article/202111/2021110914311155.png">
  <!-- Canonical links -->
  <link rel="canonical" href="https://hwame.top/20220327/channel-and-goroutine-in-go.html">
  
    <link rel="alternate" href="true" title="鴻塵" type="application/atom+xml">
  
  
    <link rel="icon" href="/favicon.png" type="image/x-icon">
  
  
<link rel="stylesheet" href="/css/style.css">

  
    <link href="//cdn.jsdelivr.net/npm/katex@0.9.0/dist/katex.min.css" rel="stylesheet">
  
  
  
    <link href="//cdn.jsdelivr.net/npm/@fancyapps/fancybox@latest/dist/jquery.fancybox.min.css" rel="stylesheet">
  
  
<meta name="generator" content="Hexo 5.4.2"></head>


<script src="https://cdn.jsdelivr.net/gh/bobcn/hexo_resize_image.js@master/hexo_resize_image.js"></script>
<body class="main-center theme-purple" itemscope itemtype="http://schema.org/WebPage">
  <header class="header" itemscope itemtype="http://schema.org/WPHeader">
  <div class="slimContent">
    <div class="navbar-header">
      
      
      <div class="profile-block text-center">
        <a id="avatar" href="https://hwame.top" target="_blank">
          <img class="img-circle img-rotate" src="https://cdn.jsdelivr.net/gh/hwame/pics@main/avatar.jpg" width="200" height="200">
        </a>
        <h2 id="name" class="hidden-xs hidden-sm">鴻塵</h2>
        <h3 id="title" class="hidden-xs hidden-sm hidden-md">Pythoner, Data Analyst</h3>
        <small id="location" class="text-muted hidden-xs hidden-sm"><i class="icon icon-map-marker"></i> 湖北-武汉</small>
      </div>
      
      <div class="search" id="search-form-wrap">

    <form class="search-form sidebar-form">
        <div class="input-group">
            <input type="text" class="search-form-input form-control" placeholder="搜索" />
            <span class="input-group-btn">
                <button type="submit" class="search-form-submit btn btn-flat" onclick="return false;"><i class="icon icon-search"></i></button>
            </span>
        </div>
    </form>
    <div class="ins-search">
  <div class="ins-search-mask"></div>
  <div class="ins-search-container">
    <div class="ins-input-wrapper">
      <input type="text" class="ins-search-input" placeholder="想要查找什么..." x-webkit-speech />
      <button type="button" class="close ins-close ins-selectable" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
    </div>
    <div class="ins-section-wrapper">
      <div class="ins-section-container"></div>
    </div>
  </div>
</div>


</div>
      <button class="navbar-toggle collapsed" type="button" data-toggle="collapse" data-target="#main-navbar" aria-controls="main-navbar" aria-expanded="false">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
    </div>
    <nav id="main-navbar" class="collapse navbar-collapse" itemscope itemtype="http://schema.org/SiteNavigationElement" role="navigation">
      <ul class="nav navbar-nav main-nav menu-highlight">
        
        
        <li class="menu-item menu-item-home">
          <a href="/.">
            
            <i class="icon icon-home-fill"></i>
            
            <span class="menu-title">首页</span>
          </a>
        </li>
        
        
        <li class="menu-item menu-item-archives">
          <a href="/archives">
            
            <i class="icon icon-archives-fill"></i>
            
            <span class="menu-title">归档</span>
          </a>
        </li>
        
        
        <li class="menu-item menu-item-categories">
          <a href="/categories">
            
            <i class="icon icon-folder-open"></i>
            
            <span class="menu-title">分类</span>
          </a>
        </li>
        
        
        <li class="menu-item menu-item-tags">
          <a href="/tags">
            
            <i class="icon icon-tags"></i>
            
            <span class="menu-title">标签</span>
          </a>
        </li>
        
        
        <li class="menu-item menu-item-repository">
          <a href="/repository">
            
            <i class="icon icon-project"></i>
            
            <span class="menu-title">资源</span>
          </a>
        </li>
        
        
        <li class="menu-item menu-item-gallery">
          <a href="/gallery">
            
            <i class="icon icon-delicious"></i>
            
            <span class="menu-title">相册</span>
          </a>
        </li>
        
        
        <li class="menu-item menu-item-links">
          <a href="/links">
            
            <i class="icon icon-friendship"></i>
            
            <span class="menu-title">友链</span>
          </a>
        </li>
        
        
        <li class="menu-item menu-item-about">
          <a href="/about">
            
            <i class="icon icon-cup-fill"></i>
            
            <span class="menu-title">关于</span>
          </a>
        </li>
        
      </ul>
      
	
    <ul class="social-links">
    	
        <li><a href="https://github.com/hwame" target="_blank" title="Github" data-toggle=tooltip data-placement=top><i class="icon icon-github"></i></a></li>
        
        <li><a href="https://weibo.com/hwamei" target="_blank" title="Weibo" data-toggle=tooltip data-placement=top><i class="icon icon-weibo"></i></a></li>
        
        <li><a href="https://www.zhihu.com/people/hwame" target="_blank" title="Zhihu" data-toggle=tooltip data-placement=top><i class="icon icon-zhihu"></i></a></li>
        
        <li><a href="https://segmentfault.com/u/hwame" target="_blank" title="Segmentfault" data-toggle=tooltip data-placement=top><i class="icon icon-segmentfault"></i></a></li>
        
        <li><a href="https://gitee.com/hwame" target="_blank" title="Gitee" data-toggle=tooltip data-placement=top><i class="icon icon-gitee"></i></a></li>
        
        <li><a href="/atom.xml" target="_blank" title="Rss" data-toggle=tooltip data-placement=top><i class="icon icon-rss"></i></a></li>
        
    </ul>

    </nav>
  </div>
</header>

  
    <aside class="sidebar" itemscope itemtype="http://schema.org/WPSideBar">
  <div class="slimContent">
    
      <div class="widget">
    <h3 class="widget-title"><i style="color:#9400D3" class="icon icon-stackexchange"></i>公告</h3>
    <div class="widget-body">
        <div id="board">
            <div class="content">
                <p>欢迎来到鴻塵的博客!<br>鴻塵的<u><a target="_blank" rel="noopener" href="https://weibo.com/hwamei" style="color:#E541E5;">微博</a></u>主页，鴻塵的<u><a target="_blank" rel="noopener" href="https://github.com/hwame" style="color:#E541E5;">Github</a></u>主页，如果我可以忘记的<u><a target="_blank" rel="noopener" href="https://www.zhihu.com/people/hwame" style="color:#E541E5;">知乎</a></u>主页。</p>
            </div>
        </div>
    </div>
</div>

    
      
  <div class="widget">
    <h3 class="widget-title"><i style="color:#9400D3" class="icon icon-tags"></i>标签云</h3>
    <div class="widget-body tagcloud">
      <a href="/tags/CentOS/" style="font-size: 13px; color: #fff">CentOS</a> <a href="/tags/Go/" style="font-size: 13.2px; color: #fff">Go</a> <a href="/tags/Hexo/" style="font-size: 13.8px; color: #fff">Hexo</a> <a href="/tags/Linux/" style="font-size: 14px; color: #fff">Linux</a> <a href="/tags/Linux%E9%83%A8%E7%BD%B2/" style="font-size: 13px; color: #fff">Linux部署</a> <a href="/tags/MongoDB/" style="font-size: 13.4px; color: #fff">MongoDB</a> <a href="/tags/Spark/" style="font-size: 13px; color: #fff">Spark</a> <a href="/tags/matplotlib/" style="font-size: 13.2px; color: #fff">matplotlib</a> <a href="/tags/miscellanea/" style="font-size: 13.6px; color: #fff">miscellanea</a> <a href="/tags/python/" style="font-size: 14px; color: #fff">python</a> <a href="/tags/shell/" style="font-size: 13.6px; color: #fff">shell</a> <a href="/tags/%E5%A4%A7%E6%95%B0%E6%8D%AE/" style="font-size: 13px; color: #fff">大数据</a> <a href="/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/" style="font-size: 13px; color: #fff">数据结构与算法</a> <a href="/tags/%E7%88%AC%E8%99%AB/" style="font-size: 13px; color: #fff">爬虫</a>
    </div>
  </div>

<script type="text/javascript">
    var everytag=document.getElementsByClassName("widget-body tagcloud")[0].children;
    for (var i = everytag.length - 1; i >= 0; i--) {
    	var r=Math.floor(Math.random()*255);
        var g=Math.floor(Math.random()*255);
        var b=Math.floor(Math.random()*255);
        everytag[i].style.background = "rgb("+r+","+g+","+b+")";
    }
</script>
    
      
  <div class="widget">
    <h3 class="widget-title"><i style="color:#9400D3" class="icon icon-folder-open"></i>分类</h3>
    <div class="widget-body">
      <ul class="category-list"><li class="category-list-item"><a class="category-list-link" href="/categories/Go/">Go</a><span class="category-list-count">2</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/Hexo/">Hexo</a><span class="category-list-count">5</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/Linux/">Linux</a><span class="category-list-count">8</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/MongoDB/">MongoDB</a><span class="category-list-count">3</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/miscellanea/">miscellanea</a><span class="category-list-count">4</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/python/">python</a><span class="category-list-count">7</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/">数据结构与算法</a><span class="category-list-count">1</span></li></ul>
    </div>
  </div>


    
      
  <div class="widget">
    <h3 class="widget-title"><i style="color:#9400D3" class="icon icon-archives-fill"></i>归档</h3>
    <div class="widget-body">
      <ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/archives/2022/">2022</a><span class="archive-list-count">4</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2021/">2021</a><span class="archive-list-count">9</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2020/">2020</a><span class="archive-list-count">17</span></li></ul>
    </div>
  </div>


    
      
  <div class="widget">
    <h3 class="widget-title"><i style="color:#9400D3" class="icon icon-shu-fill"></i>最新文章</h3>
    <div class="widget-body">
      <ul class="recent-post-list list-unstyled ">
        
          <li>
            
            <div class="item-thumb">
              <a href="/20220327/channel-and-goroutine-in-go.html" class="thumb">
    
    
        <span style="background-image:url(https://cdn.jsdelivr.net/gh/hwame/pics@main/post-pics/icon-golang.jpeg)" alt="Go语言中的通道" class="thumb-image"></span>
    
</a>

            </div>
            
            <div class="item-inner">
              <p class="item-category">
                <a class="category-link" href="/categories/Go/">Go</a>
              </p>
              <p class="item-title">
                <a href="/20220327/channel-and-goroutine-in-go.html" class="title">Go语言中的通道</a>
              </p>
              <p class="item-date">
                <time datetime="2022-03-27T11:06:16.000Z" itemprop="datePublished">2022-03-27</time>
              </p>
            </div>
          </li>
          
          <li>
            
            <div class="item-thumb">
              <a href="/20220323/when-to-use-pointer-in-go.html" class="thumb">
    
    
        <span style="background-image:url(https://cdn.jsdelivr.net/gh/hwame/pics@main/post-pics/icon-golang.jpeg)" alt="Go语言什么时候使用指针" class="thumb-image"></span>
    
</a>

            </div>
            
            <div class="item-inner">
              <p class="item-category">
                <a class="category-link" href="/categories/Go/">Go</a>
              </p>
              <p class="item-title">
                <a href="/20220323/when-to-use-pointer-in-go.html" class="title">Go语言什么时候使用指针</a>
              </p>
              <p class="item-date">
                <time datetime="2022-03-23T12:49:57.000Z" itemprop="datePublished">2022-03-23</time>
              </p>
            </div>
          </li>
          
          <li>
            
            <div class="item-thumb">
              <a href="/20220228/awesome-images-in-markdown.html" class="thumb">
    
    
        <span style="background-image:url(https://cdn.jsdelivr.net/gh/hwame/pics@main/avatar.jpg)" alt="Markdown中图片的高级用法" class="thumb-image"></span>
    
</a>

            </div>
            
            <div class="item-inner">
              <p class="item-category">
                <a class="category-link" href="/categories/miscellanea/">miscellanea</a>
              </p>
              <p class="item-title">
                <a href="/20220228/awesome-images-in-markdown.html" class="title">Markdown中图片的高级用法</a>
              </p>
              <p class="item-date">
                <time datetime="2022-02-28T15:27:50.000Z" itemprop="datePublished">2022-02-28</time>
              </p>
            </div>
          </li>
          
          <li>
            
            <div class="item-thumb">
              <a href="/20220116/shell-regular-expression.html" class="thumb">
    
    
        <span style="background-image:url(https://cdn.jsdelivr.net/gh/hwame/pics@main/post-pics/linux-icon.png)" alt="Shell正则表达式" class="thumb-image"></span>
    
</a>

            </div>
            
            <div class="item-inner">
              <p class="item-category">
                <a class="category-link" href="/categories/Linux/">Linux</a>
              </p>
              <p class="item-title">
                <a href="/20220116/shell-regular-expression.html" class="title">Shell正则表达式</a>
              </p>
              <p class="item-date">
                <time datetime="2022-01-16T14:13:05.000Z" itemprop="datePublished">2022-01-16</time>
              </p>
            </div>
          </li>
          
          <li>
            
            <div class="item-thumb">
              <a href="/20211009/install-linux-on-android-phone.html" class="thumb">
    
    
        <span style="background-image:url(https://cdn.jsdelivr.net/gh/hwame/pics@main/avatar.jpg)" alt="如何在安卓手机上安装Linux发行版" class="thumb-image"></span>
    
</a>

            </div>
            
            <div class="item-inner">
              <p class="item-category">
                <a class="category-link" href="/categories/miscellanea/">miscellanea</a>
              </p>
              <p class="item-title">
                <a href="/20211009/install-linux-on-android-phone.html" class="title">如何在安卓手机上安装Linux发行版</a>
              </p>
              <p class="item-date">
                <time datetime="2021-10-09T15:16:37.000Z" itemprop="datePublished">2021-10-09</time>
              </p>
            </div>
          </li>
          
      </ul>
    </div>
  </div>
  

    
  </div>
</aside>

  
  
<aside class="sidebar sidebar-toc collapse" id="collapseToc" itemscope itemtype="http://schema.org/WPSideBar">
  <div class="slimContent">
    <nav id="toc" class="article-toc">
      <h3 class="toc-title">文章目录</h3>
      <ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#1-%E5%89%8D%E8%A8%80"><span class="toc-number">1.</span> <span class="toc-text">1.前言</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-%E9%80%9A%E9%81%93%E7%AE%80%E4%BB%8B"><span class="toc-number">2.</span> <span class="toc-text">2.通道简介</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#2-1-%E5%A3%B0%E6%98%8E"><span class="toc-number">2.1.</span> <span class="toc-text">2.1.声明</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-2-%E8%AF%BB%E5%86%99"><span class="toc-number">2.2.</span> <span class="toc-text">2.2.读写</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-3-%E9%80%9A%E9%81%93%E8%AF%A6%E8%A7%A3"><span class="toc-number">2.3.</span> <span class="toc-text">2.3.通道详解</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#2-3-1-%E4%BE%8B%E5%AD%90"><span class="toc-number">2.3.1.</span> <span class="toc-text">2.3.1.例子</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-3-2-%E6%AD%BB%E9%94%81"><span class="toc-number">2.3.2.</span> <span class="toc-text">2.3.2.死锁</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-3-3-%E5%85%B3%E9%97%AD%E9%80%9A%E9%81%93"><span class="toc-number">2.3.3.</span> <span class="toc-text">2.3.3.关闭通道</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-3-4-%E7%BC%93%E5%86%B2%E5%8C%BA"><span class="toc-number">2.3.4.</span> <span class="toc-text">2.3.4.缓冲区</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-3-5-%E9%80%9A%E9%81%93%E7%9A%84%E9%95%BF%E5%BA%A6%E5%92%8C%E5%AE%B9%E9%87%8F"><span class="toc-number">2.3.5.</span> <span class="toc-text">2.3.5.通道的长度和容量</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-3-6-%E5%8D%95%E5%90%91%E9%80%9A%E9%81%93"><span class="toc-number">2.3.6.</span> <span class="toc-text">2.3.6.单向通道</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-3-7-Select"><span class="toc-number">2.3.7.</span> <span class="toc-text">2.3.7.Select</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-3-8-default-case-%E5%9D%97"><span class="toc-number">2.3.8.</span> <span class="toc-text">2.3.8.default case 块</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-3-9-%E7%A9%BA-select"><span class="toc-number">2.3.9.</span> <span class="toc-text">2.3.9.空 select</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-3-10-Deadlock"><span class="toc-number">2.3.10.</span> <span class="toc-text">2.3.10.Deadlock</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-3-11-nil%E9%80%9A%E9%81%93"><span class="toc-number">2.3.11.</span> <span class="toc-text">2.3.11.nil通道</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-4-%E5%A4%9A%E5%8D%8F%E7%A8%8B%E5%8D%8F%E5%90%8C%E5%B7%A5%E4%BD%9C"><span class="toc-number">2.4.</span> <span class="toc-text">2.4.多协程协同工作</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-5-WaitGroup"><span class="toc-number">2.5.</span> <span class="toc-text">2.5.WaitGroup</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#2-5-1-%E7%AE%80%E4%BB%8B"><span class="toc-number">2.5.1.</span> <span class="toc-text">2.5.1.简介</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-5-2-%E5%B7%A5%E4%BD%9C%E6%B1%A0"><span class="toc-number">2.5.2.</span> <span class="toc-text">2.5.2.工作池</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-5-3-Mutex"><span class="toc-number">2.5.3.</span> <span class="toc-text">2.5.3.Mutex</span></a></li></ol></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3-%E7%BB%93%E8%AF%AD"><span class="toc-number">3.</span> <span class="toc-text">3.结语</span></a></li></ol>
    </nav>
  </div>
</aside>

<main class="main" role="main">
  <div class="content">
  <article id="post-channel-and-goroutine-in-go" class="article article-type-post" itemscope itemtype="http://schema.org/BlogPosting">
    
    <div class="article-header">
      
        
  
    <h1 class="article-title" itemprop="name">
      Go语言中的通道
    </h1>
  

      
      <div class="article-meta">
        <span class="article-date">
    <i class="icon icon-calendar"></i>
	<a href="/20220327/channel-and-goroutine-in-go.html" class="article-date">
	  发布于 <time datetime="2022-03-27T11:06:16.000Z" itemprop="datePublished">2022-03-27</time>
	</a>
</span>
<span class="article-date">
    <i class="icon icon-calendar-check"></i>
	<a href="/20220327/channel-and-goroutine-in-go.html" class="article-date">
	  更新于 <time datetime="2022-03-27T12:00:41.000Z" itemprop="dateUpdated">2022-03-27</time>
	</a>
</span>
        
  <span class="article-category">
    <i class="icon icon-folder"></i>
    <a class="article-category-link" href="/categories/Go/">Go</a>
  </span>

        
  <span class="article-tag">
    <i class="icon icon-tags"></i>
	<a class="article-tag-link-link" href="/tags/Go/" rel="tag">Go</a>
  </span>


        

	<span class="article-read hidden-xs">
    	<i class="icon icon-eye-fill" aria-hidden="true"></i>
    	<span id="/20220327/channel-and-goroutine-in-go.html" class="leancloud_visitors"  data-flag-title="Go语言中的通道">
			<span class="leancloud-visitors-count">0</span>
		</span>
    </span>

        <span class="post-comment"><i class="icon icon-comment"></i> <a href="/20220327/channel-and-goroutine-in-go.html#comments" class="article-comment-link">评论</a></span>
        
	
		<span class="post-wordcount hidden-xs" itemprop="wordCount">字数统计: 8.3k(字)</span>
	
	
		<span class="post-readcount hidden-xs" itemprop="timeRequired">阅读时长: 34(分)</span>
	

      </div>
      <div style="background-color:#D7BDE2;border:1px solid #D7BDE2;border-radius:10px;padding:5px">
          <b>温馨提示</b>：点击页面下方<i style="color:red" class="icon icon-anchor"></i>以展开或折叠目录
      </div>
    </div>
    <div class="article-entry marked-body" itemprop="articleBody">
      
        <p>摘要：看完这篇，Go语言的通道及Goroutine你就都会了…<span id="more"></span></p>
<blockquote>
<p><font size=5><b>文章说明</b></font><br><strong>文章作者：</strong><a href="https://hwame.top">鴻塵</a><br><strong>文章链接：</strong><a href="https://hwame.top/20220327/channel-and-goroutine-in-go.html">https://hwame.top/20220327/channel-and-goroutine-in-go.html</a><br><strong>原文链接：</strong><a target="_blank" rel="noopener" href="https://mp.weixin.qq.com/s/-N1qUEE090wMpI6TWmFTWg">https://mp.weixin.qq.com/s/-N1qUEE090wMpI6TWmFTWg</a><br><strong>参考资料：</strong></p>
<ul>
<li><a target="_blank" rel="noopener" href="https://mp.weixin.qq.com/s/-N1qUEE090wMpI6TWmFTWg">看完这篇，Go语言的通道及Goroutine你就都会了</a></li>
<li><a target="_blank" rel="noopener" href="https://www.jb51.net/article/228730.htm">Go语言七篇入门教程四：通道及Goroutine</a></li>
<li><a target="_blank" rel="noopener" href="https://learnku.com/go/t/31890">深入学习 Go 并发编程之通道</a></li>
</ul>
</blockquote>
<h2 id="1-前言"><a href="#1-前言" class="headerlink" title="1.前言"></a>1.前言</h2><p>在go社区有这样一句话：不要通过共享内存来通信，而是通过通信来共享内存。</p>
<p>go官方是建议使用管道通信的方式来进行并发。<br> <strong>通道</strong>  是用于协程间交流的通信载体。严格地来说，通道就是数据传输的管道，数据通过这根管道被 “传入” 或被 “读出”。 因此协程可以发送数据到通道中，而另一个协程可以从该通道中读取数据。</p>
<p>在这里就要引入一个新名词： <strong>协程</strong><br>将线程再细分为多个协程，比如说是一条流水线上的多人协作。那么就可以减少各个线程内部的等待时间。</p>
<h2 id="2-通道简介"><a href="#2-通道简介" class="headerlink" title="2.通道简介"></a>2.通道简介</h2><p>Go 提供一个 chan 关键词去创建一个通道。一个通道只能传入一种类型的数据，其他的数据类型不允许被传输。<br><img src="https://img.jbzj.com/file_images/article/202111/2021110914311155.png" alt="picture"></p>
<p>将线程再分成更细的协程，使得中间等待时候更少，提高效率！</p>
<p><img src="https://img.jbzj.com/file_images/article/202111/2021110914311156.png" alt="picture"></p>
<h3 id="2-1-声明"><a href="#2-1-声明" class="headerlink" title="2.1.声明"></a>2.1.声明</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">var</span> channel <span class="keyword">chan</span> <span class="type">int</span></span><br><span class="line">    <span class="comment">//声明了一个可以传入 int 类型数据的通道 channel 。</span></span><br><span class="line"></span><br><span class="line">    fmt.Println(channel)  </span><br><span class="line">    <span class="comment">//程序会打印nil, 因为通道的 0 值是 nil。</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>一个 nil 通道是没有用的。你不能向它传递数据或者读取数据。<br>因此，我们必须使用 make 函数器创建一个可以使用的通道。</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    channel := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">int</span>) </span><br><span class="line">    <span class="comment">//声明了一个可以传入 int 类型数据的通道 channel 。</span></span><br><span class="line"></span><br><span class="line">    fmt.Println(channel)  </span><br><span class="line">    <span class="comment">//程序会打印channel的地址。 0xc0000180c0</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>它是一个指针内存地址。通道变量默认是一个指针。多数情况下，当你想要和一个协程沟通的时候，你可以给函数或者方法传递一个通道作为参数。当从协程接收到通道参数后，你不需要再对其进行解引用就可以从通道接收或者发送数据。</p>
<h3 id="2-2-读写"><a href="#2-2-读写" class="headerlink" title="2.2.读写"></a>2.2.读写</h3><p>Go 语言提供一个非常简洁的左箭头语法  <code>&lt;-</code>  去从通道读写数据。<br>有变量接受管道值<br><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">channel &lt;- data</span><br></pre></td></tr></table></figure></p>
<p>上面的代码意味着我们想要把 data 数据推入到通道 channel 中，注意看箭头的指向。</p>
<p>它表明是从 data数据 to到 通道 channel。因此我们可以当作我们正在把 data 推入到通道 channel。<br>无变量接受管道值<br><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;- data</span><br></pre></td></tr></table></figure></p>
<p>这个语句不会把数据传输给任何变量，但是仍然是一个有效的语句。</p>
<p>上面的通道操作默认是阻塞的。<br>在以前的课程中，我们知道可以使用 time.Sleep 去阻塞一个通道。通道操作本质上是阻塞的。当一些数据被写入通道，对应的协程将阻塞直到有其他的协程可以从此通道接收数据。</p>
<p>通道操作会通知调度器去调度其他的协程，这就是为什么程序不会一直阻塞在一个协程。通道的这些特性在不同的协程沟通的时候非常有用，它避免了我们使用锁或者一些 hack 手段去达到阻塞协程的目的。</p>
<h3 id="2-3-通道详解"><a href="#2-3-通道详解" class="headerlink" title="2.3.通道详解"></a>2.3.通道详解</h3><h4 id="2-3-1-例子"><a href="#2-3-1-例子" class="headerlink" title="2.3.1.例子"></a>2.3.1.例子</h4><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Rush</span><span class="params">(c <span class="keyword">chan</span> <span class="type">string</span>)</span></span> &#123;</span><br><span class="line">    fmt.Println(<span class="string">&quot;Hello &quot;</span>+ &lt;-c + <span class="string">&quot;!&quot;</span>)</span><br><span class="line">    <span class="comment">// 声明一个函数 greet, 这个函数的参数 c 是一个 string 类型的通道。</span></span><br><span class="line">    <span class="comment">// 在这个函数中，我们从通道 c 中接收数据并打印到控制台上。</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    fmt.Println(<span class="string">&quot;Main Start&quot;</span>) </span><br><span class="line">    <span class="comment">// main 函数的第一个语句是打印 main start 到控制台。</span></span><br><span class="line"></span><br><span class="line">    channel := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">string</span>)</span><br><span class="line">    <span class="comment">// 在 main 函数中使用 make 函数创建一个 string 类型的通道赋值给 ‘ channel &#x27; 变量</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">go</span> Rush(channel)        </span><br><span class="line">    <span class="comment">// 把 channel 通道传递给 greet 函数并用 go 关键词以协程方式运行它。</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 此时，程序有两个协程并且正在调度运行的是 main goroutine 主函数 </span></span><br><span class="line"></span><br><span class="line">    channel &lt;- <span class="string">&quot;DEMO&quot;</span></span><br><span class="line">       </span><br><span class="line">    <span class="comment">// 给通道 channel 传入一个数据 DEMO.</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 此时主线程将阻塞直到有协程接收这个数据. Go 的调度器开始调度 greet 协程接收通道 channel 的数据 </span></span><br><span class="line"></span><br><span class="line">    fmt.Println(<span class="string">&quot;Main Stop&quot;</span>)   </span><br><span class="line">    <span class="comment">// 然后主线程激活并且执行后面的语句，打印 main stopped</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">Main Start</span></span><br><span class="line"><span class="comment">Hello DEMO!</span></span><br><span class="line"><span class="comment">Main Stop</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure>
<h4 id="2-3-2-死锁"><a href="#2-3-2-死锁" class="headerlink" title="2.3.2.死锁"></a>2.3.2.死锁</h4><p><img src="https://img.jbzj.com/file_images/article/202111/2021110914311257.png" alt="picture"></p>
<p>当通道读写数据时，所在协程会阻塞并且调度控制权会转移到其他未阻塞的协程。<br>如果当前协程正在从一个没有任何值的通道中读取数据，那么当前协程会阻塞并且等待其他协程往此通道写入值。</p>
<p>因此，读操作将被阻塞。类似的，如果你发送数据到一个通道，它将阻塞当前协程直到有其他协程从通道中读取数据。此时写操作将阻塞 。</p>
<p>下面是一个主线程在进行通道操作的时候造成死锁的例子<br><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    fmt.Println(<span class="string">&quot;main start&quot;</span>)</span><br><span class="line">    <span class="comment">// main 函数的第一个语句是打印 main start 到控制台。</span></span><br><span class="line"></span><br><span class="line">    channel := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">string</span>)</span><br><span class="line">    <span class="comment">// 在 main 函数中使用 make 函数创建一个 string 类型的通道赋值给 ‘ channel &#x27; 变量</span></span><br><span class="line"></span><br><span class="line">    channel &lt;- <span class="string">&quot;GoLang&quot;</span></span><br><span class="line">    <span class="comment">// 给通道 channel 传入一个数据 DEMO.</span></span><br><span class="line">    <span class="comment">// 此时主线程将阻塞直到有协程接收这个数据. Go 的调度器开始调度协程接收通道 channel 的数据</span></span><br><span class="line">    <span class="comment">// 但是由于没有协程接受，没有协程是可被调度的。所有协程都进入休眠状态，即是主程序阻塞了。</span></span><br><span class="line">    fmt.Println(<span class="string">&quot;main stop&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">报错</span></span><br><span class="line"><span class="comment">main start</span></span><br><span class="line"><span class="comment">fatal error: all goroutines are asleep - deadlock!  //所有协程都进入休眠状态,死锁</span></span><br><span class="line"><span class="comment">goroutine 1 [chan send]:</span></span><br><span class="line"><span class="comment">main.main()</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure></p>
<h4 id="2-3-3-关闭通道"><a href="#2-3-3-关闭通道" class="headerlink" title="2.3.3.关闭通道"></a>2.3.3.关闭通道</h4><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">RushChan</span><span class="params">(c <span class="keyword">chan</span> <span class="type">string</span>)</span></span> &#123;</span><br><span class="line">    &lt;- c</span><br><span class="line">    fmt.Println(<span class="string">&quot;1&quot;</span>)</span><br><span class="line">    &lt;- c</span><br><span class="line">    fmt.Println(<span class="string">&quot;2&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    fmt.Println(<span class="string">&quot;main start&quot;</span>)</span><br><span class="line">    c := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">string</span>, <span class="number">1</span>)</span><br><span class="line">    <span class="keyword">go</span> RushChan(c)</span><br><span class="line">    c &lt;- <span class="string">&quot;Demo1&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="built_in">close</span>(c)</span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">    不能向一个关了的channel发信息</span></span><br><span class="line"><span class="comment">    main start</span></span><br><span class="line"><span class="comment">    panic: send on closed channel</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line"></span><br><span class="line">    c &lt;- <span class="string">&quot;Demo2&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">//close(c)</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">    close 放这里的话可以</span></span><br><span class="line"><span class="comment">    main start</span></span><br><span class="line"><span class="comment">    1</span></span><br><span class="line"><span class="comment">    2</span></span><br><span class="line"><span class="comment">    Main Stop</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line"></span><br><span class="line">    fmt.Println(<span class="string">&quot;Main Stop&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>第一个操作  <code>c &lt;- &quot;Demo2&quot;</code>  将阻塞协程直到有其他协程从此通道中读取数据，因此 greet 会被调度器调度执行。<br>第一个操作  <code>&lt;-c</code>  是非阻塞的 因为现在通道 <code>c</code> 有数据可读。<br>第二个操作  <code>&lt;-c</code> 将被阻塞因为通道 <code>c</code> 已经没数据可读.<br>此时 <code>main</code> 协程将被激活并且程序执行 <code>close(c)</code> 关闭通道操作。</p>
<h4 id="2-3-4-缓冲区"><a href="#2-3-4-缓冲区" class="headerlink" title="2.3.4.缓冲区"></a>2.3.4.缓冲区</h4><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">c := <span class="built_in">make</span>(<span class="keyword">chan</span> Type, n)</span><br></pre></td></tr></table></figure>
<p>当缓冲区参数不是 0 的时候。协程将不会阻塞除非缓冲区被填满。</p>
<p>当缓冲区满了之后，想要再往缓冲区发送数据只有等到有其他协程从缓冲区接收数据， 此时的发送协程是阻塞的。</p>
<p>有一点需要注意， 读缓冲区的操作是渴望式读取，意味着一旦读操作开始它将读取缓冲区所有数据，直到缓冲区为空。</p>
<p>原理上来说读操作的协程将不会阻塞直到缓冲区为空。<br><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">RushChan</span><span class="params">(c <span class="keyword">chan</span> <span class="type">string</span>)</span></span> &#123;</span><br><span class="line">    <span class="keyword">for</span> &#123;</span><br><span class="line">        val ,_ := &lt;-c</span><br><span class="line">        fmt.Println(val)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    fmt.Println(<span class="string">&quot;Main Start&quot;</span>)</span><br><span class="line">    c := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">string</span>, <span class="number">1</span>)</span><br><span class="line">    <span class="keyword">go</span> RushChan(c)</span><br><span class="line">    c &lt;- <span class="string">&quot;Demo1&quot;</span></span><br><span class="line">    <span class="comment">//结果1</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">//c &lt;- &quot;Demo2&quot;  //结果2</span></span><br><span class="line"></span><br><span class="line">    fmt.Println(<span class="string">&quot;Main Stop&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">结果1：</span></span><br><span class="line"><span class="comment">Main Start</span></span><br><span class="line"><span class="comment">Main Stop</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">结果2：</span></span><br><span class="line"><span class="comment">Main Start</span></span><br><span class="line"><span class="comment">Join</span></span><br><span class="line"><span class="comment">Mike</span></span><br><span class="line"><span class="comment">Main Stop</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure></p>
<p>由于这是一个缓冲的通道，当我只有 <code>c &lt;- Demo1</code> 的时候，这里面只是满了，但是是不会阻塞的。所以子协程接受到了这个数据 <code>Demo1</code> ，但是由于是非阻塞，所以主线程没有被阻塞，并没有等子协程完成就结束了，结果1就是这样出现了。</p>
<p>当加多一个 <code>c &lt;- Demo2</code>  的时候，这时就要等缓冲区空了，也就是等有协程把 <code>Demo1</code> 读取，所以就会导致主线程阻塞，此时的结果就是结果2了。<br><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">RushChan</span><span class="params">(c <span class="keyword">chan</span> <span class="type">string</span>)</span></span> &#123;</span><br><span class="line">    <span class="keyword">for</span> &#123;</span><br><span class="line">        val ,_ := &lt;-c</span><br><span class="line">        fmt.Println(val)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    c := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">int</span>,<span class="number">3</span>)</span><br><span class="line">    c &lt;- <span class="number">1</span></span><br><span class="line">    c &lt;- <span class="number">2</span></span><br><span class="line">    c &lt;- <span class="number">3</span></span><br><span class="line">    <span class="built_in">close</span>(c)</span><br><span class="line">    <span class="keyword">for</span> elem := <span class="keyword">range</span> c &#123;</span><br><span class="line">        fmt.Println(elem)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>这里虽然关闭了通道，但是其实数据不仅在通道里面，数据还在缓冲区中的，我们依然可以读取到这个数据。</p>
<h4 id="2-3-5-通道的长度和容量"><a href="#2-3-5-通道的长度和容量" class="headerlink" title="2.3.5.通道的长度和容量"></a>2.3.5.通道的长度和容量</h4><p>和切片类似，一个缓冲通道也有长度和容量。<br>通道的长度是其内部缓冲队列未读的数据量，而通道的容量是缓冲区可最大盛放的数据量。</p>
<p>我们可以使用 len 函数去计算通道的长度，使用 cap 函数去获得通道的容量。和切片用法神似<br><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">RushChan</span><span class="params">(c <span class="keyword">chan</span> <span class="type">string</span>)</span></span> &#123;</span><br><span class="line">    <span class="keyword">for</span> &#123;</span><br><span class="line">        val ,_ := &lt;-c</span><br><span class="line">        fmt.Println(val)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    c := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">int</span>,<span class="number">3</span>)</span><br><span class="line">    c &lt;- <span class="number">1</span></span><br><span class="line">    c &lt;- <span class="number">2</span></span><br><span class="line"></span><br><span class="line">    fmt.Println(<span class="string">&quot;长度： &quot;</span>,<span class="built_in">len</span>(c))</span><br><span class="line">    fmt.Println(&lt;-c)</span><br><span class="line">    fmt.Println(<span class="string">&quot;长度： &quot;</span>,<span class="built_in">len</span>(c))</span><br><span class="line">    fmt.Println(&lt;-c)</span><br><span class="line">    fmt.Println(<span class="string">&quot;长度： &quot;</span>,<span class="built_in">len</span>(c))</span><br><span class="line">    fmt.Println(<span class="string">&quot;容量： &quot;</span>,<span class="built_in">cap</span>(c))</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">结果：</span></span><br><span class="line"><span class="comment">长度：  2</span></span><br><span class="line"><span class="comment">1</span></span><br><span class="line"><span class="comment">长度：  1</span></span><br><span class="line"><span class="comment">2</span></span><br><span class="line"><span class="comment">长度：  0</span></span><br><span class="line"><span class="comment">容量：  3</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure></p>
<p>这个 c 通道容量为 3，但只盛放了 2 个数据。Go 就不用去阻塞主线程去调度其他协程。你也可以在主线程中去读取这些数据，因为虽然通道没有放满，也不会阻止你去从通道读取数据。</p>
<h4 id="2-3-6-单向通道"><a href="#2-3-6-单向通道" class="headerlink" title="2.3.6.单向通道"></a>2.3.6.单向通道</h4><p>目前为止，我们已经学习到可以双向传递数据的通道，或者说，我们可以对通道做读操作和写操作。但是事实上我们也可以创建单向通道。比如只读通道只允许读操作，只写通道只允许写操作。<br>单向通道也可以使用 make 函数创建，不过需要额外加一个箭头语法。<br><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">roc := <span class="built_in">make</span>(&lt;-<span class="keyword">chan</span> <span class="type">int</span>)</span><br><span class="line">soc := <span class="built_in">make</span>(<span class="keyword">chan</span>&lt;- <span class="type">int</span>)</span><br></pre></td></tr></table></figure></p>
<p>在上面的程序中， roc 是一个只读通道，<code>&lt;-</code> 在 chan 关键词前面。 soc is 只写通道，<code>&lt;-</code> 在 chan 关键词后面。 他们也算不同的数据类型。</p>
<p>但是单向通道有什么作用呢 ?<br>使用单向通道可以 提高程序的类型安全性， 使得程序不容易出错。</p>
<p>但是假如你在一个协程中只需要读操作某通道，但是在主线程中却需要读写操作这个通道该怎么办呢？</p>
<p>幸运的是 Go 提供了一个简单的语法去把双向通道转化为单向通道。<br><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">greet</span><span class="params">(roc &lt;-<span class="keyword">chan</span> <span class="type">string</span>)</span></span> &#123;</span><br><span class="line">    fmt.Println(<span class="string">&quot;Hello &quot;</span> + &lt;-roc ,<span class="string">&quot;!&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    fmt.Println(<span class="string">&quot;Main Start&quot;</span>)</span><br><span class="line">    c := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">string</span>)</span><br><span class="line">    <span class="keyword">go</span> greet(c)</span><br><span class="line">    c &lt;- <span class="string">&quot;Demo&quot;</span></span><br><span class="line"></span><br><span class="line">    fmt.Println(<span class="string">&quot;Main Stop&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">结果</span></span><br><span class="line"><span class="comment">Main Start</span></span><br><span class="line"><span class="comment">Hello Demo !</span></span><br><span class="line"><span class="comment">Main Stop</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure></p>
<p>我们修改 greet 协程函数，把参数 c 类型从双向通道改成单向接收通道。<br>现在我们只能从通道中读取数据，通道上的任何写入操作将会发生错误：</p>
<blockquote>
<p><code>“invalid operation: roc &lt;- “Temp” (send to receive-only type &lt;-chan string)”.</code></p>
</blockquote>
<p><img src="https://img.jbzj.com/file_images/article/202111/2021110914311258.png" alt="picture"></p>
<h4 id="2-3-7-Select"><a href="#2-3-7-Select" class="headerlink" title="2.3.7.Select"></a>2.3.7.Select</h4><p>select 和 switch 很像，它不需要输入参数，并且仅仅被使用在通道操作上。<br>Select 语句被用来执行多个通道操作的一个和其附带的 case 块代码。</p>
<p>原理</p>
<p>让我们来看下面的例子，讨论下其执行原理<br><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">    <span class="string">&quot;time&quot;</span></span><br><span class="line">)</span><br><span class="line"><span class="keyword">var</span> start time.Time</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">init</span><span class="params">()</span></span> &#123;</span><br><span class="line">    start = time.Now()</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">service1</span><span class="params">(c <span class="keyword">chan</span> <span class="type">string</span>)</span></span> &#123;</span><br><span class="line">    time.Sleep(<span class="number">3</span> * time.Second)</span><br><span class="line">    c &lt;- <span class="string">&quot;Hello from service 1&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">service2</span><span class="params">(c <span class="keyword">chan</span> <span class="type">string</span>)</span></span> &#123;</span><br><span class="line">    time.Sleep(<span class="number">5</span> * time.Second)</span><br><span class="line">    c &lt;- <span class="string">&quot;Hello from service 2&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    fmt.Println(<span class="string">&quot;main start&quot;</span>, time.Since(start))</span><br><span class="line">    chan1 := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">string</span>)</span><br><span class="line">    chan2 := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">string</span>)</span><br><span class="line">    <span class="keyword">go</span> service1(chan1)</span><br><span class="line">    <span class="keyword">go</span> service2(chan2)</span><br><span class="line">    <span class="keyword">select</span> &#123;</span><br><span class="line">    <span class="keyword">case</span> res := &lt;-chan1:</span><br><span class="line">        fmt.Println(<span class="string">&quot;Response form service 1&quot;</span>, res, time.Since(start))</span><br><span class="line">    <span class="keyword">case</span> res := &lt;-chan2:</span><br><span class="line">        fmt.Println(<span class="string">&quot;Response form service 2&quot;</span>, res, time.Since(start))</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    fmt.Println(<span class="string">&quot;main stop &quot;</span>,time.Since(start))</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">结果：</span></span><br><span class="line"><span class="comment">main start 0s</span></span><br><span class="line"><span class="comment">Response form service 1 Hello from service 1 3.0018445s</span></span><br><span class="line"><span class="comment">main stop  3.0019815s</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure></p>
<p>从上面的程序来看，我们知道 select 语句和 switch 很像，不同点是用通道读写操作代替了布尔操作。通道将被阻塞，除非它有默认的 default 块 (之后将介绍)。一旦某个 case 条件执行，它将不阻塞。</p>
<p>所以一个 case 条件什么时候执行呢 ?</p>
<p>如果所有的 case 语句（通道操作）被阻塞，那么 select 语句将阻塞直到这些 case 条件的一个不阻塞（通道操作），case 块执行。<br>如果有多个 case 块（通道操作）都没有阻塞，那么运行时将随机选择一个不阻塞的 case 块立即执行。</p>
<p>为了演示上面的程序，我们开启两个协程并传入对应的通道变量。然后我们写一个带有两个 case 操作的 select 语句。 一个 case 操作从 chan1 读数据，另外一个从 chan2 读数据。这两个通道都是无缓冲的 , 读操作将被阻塞 。所以 select 语句将阻塞。因此 select 将等待，直到有 case 语句不阻塞。</p>
<ul>
<li>当程序执行到select语句后，主线程将阻塞并开始调度 service1 和service2协程。 service1 休眠 3 秒 后未阻塞的把数据写入通道 chan1 与其类似，service2等待 5 秒 后未阻塞的把数据写入通道chan2</li>
<li>因为 service1 比 service2 早一步执行完毕，case 1 将首先调度执行，其他的 cases 块 (这里指 case 2) 将被忽略。 一旦 case 块执行完毕， main 线程将开始继续执行。</li>
</ul>
<p>所以并没有输出case2的结果</p>
<p>上述程序真实模拟了一个数百万请求的服务器负载均衡的例子，它从多个有效服务中返回其中一个响应。<br>使用协程，通道和 select 语句，我们可以向多个服务器请求数据并获取其中最快响应的那个。</p>
<p>为了模拟上面哪个 case 块率先返回数据，我们可以直接去掉 Sleep 函数调用。</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">    <span class="string">&quot;time&quot;</span></span><br><span class="line">)</span><br><span class="line"><span class="keyword">var</span> start time.Time</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">init</span><span class="params">()</span></span> &#123;</span><br><span class="line">    start = time.Now()</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">service1</span><span class="params">(c <span class="keyword">chan</span> <span class="type">string</span>)</span></span> &#123;</span><br><span class="line">    c &lt;- <span class="string">&quot;Hello from service 1&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">service2</span><span class="params">(c <span class="keyword">chan</span> <span class="type">string</span>)</span></span> &#123;</span><br><span class="line">    c &lt;- <span class="string">&quot;Hello from service 2&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    fmt.Println(<span class="string">&quot;main start&quot;</span>, time.Since(start))</span><br><span class="line"></span><br><span class="line">    chan1 := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">string</span>)</span><br><span class="line">    chan2 := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">string</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">go</span> service1(chan1)</span><br><span class="line">    <span class="keyword">go</span> service2(chan2)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">select</span> &#123;</span><br><span class="line">    <span class="keyword">case</span> res := &lt;-chan1:</span><br><span class="line">        fmt.Println(<span class="string">&quot;Response form service 1&quot;</span>, res, time.Since(start))</span><br><span class="line">    <span class="keyword">case</span> res := &lt;-chan2:</span><br><span class="line">        fmt.Println(<span class="string">&quot;Response form service 2&quot;</span>, res, time.Since(start))</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    fmt.Println(<span class="string">&quot;main stop &quot;</span>,time.Since(start))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<blockquote>
<p>结果一：<br>main start 0s<br>Response form service 1 Hello from service 1 539.3µs<br>main stop 539.3µs<br>结果二：<br>main start 0s<br>Response form service 2 Hello from service 2 0s<br>main stop 0s</p>
</blockquote>
<p>结果一共有2个不同的结果</p>
<p>为了证明当所有 case 块都是非阻塞的时候，golang 会随机选择一个代码块执行打印 response，我们使用缓冲通道来改造程序。</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">    <span class="string">&quot;time&quot;</span></span><br><span class="line">)</span><br><span class="line"><span class="keyword">var</span> start time.Time</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">init</span><span class="params">()</span></span> &#123;</span><br><span class="line">    start = time.Now()</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">service1</span><span class="params">(c <span class="keyword">chan</span> <span class="type">string</span>)</span></span> &#123;</span><br><span class="line">    c &lt;- <span class="string">&quot;Hello from service 1&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">service2</span><span class="params">(c <span class="keyword">chan</span> <span class="type">string</span>)</span></span> &#123;</span><br><span class="line">    c &lt;- <span class="string">&quot;Hello from service 2&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    fmt.Println(<span class="string">&quot;main start&quot;</span>, time.Since(start))</span><br><span class="line">    chan1 := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">string</span>,<span class="number">2</span>)</span><br><span class="line">    chan2 := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">string</span>,<span class="number">2</span>)</span><br><span class="line">    chan1 &lt;- <span class="string">&quot;Value 1&quot;</span></span><br><span class="line">    chan1 &lt;- <span class="string">&quot;Value 2&quot;</span></span><br><span class="line">    chan2 &lt;- <span class="string">&quot;Value 1&quot;</span></span><br><span class="line">    chan2 &lt;- <span class="string">&quot;Value 2&quot;</span></span><br><span class="line">    <span class="keyword">select</span> &#123;</span><br><span class="line">    <span class="keyword">case</span> res := &lt;-chan1:</span><br><span class="line">        fmt.Println(<span class="string">&quot;Response form service 1&quot;</span>, res, time.Since(start))</span><br><span class="line">    <span class="keyword">case</span> res := &lt;-chan2:</span><br><span class="line">        fmt.Println(<span class="string">&quot;Response form service 2&quot;</span>, res, time.Since(start))</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    fmt.Println(<span class="string">&quot;main stop &quot;</span>,time.Since(start))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>上述的程序的结果是有不同的</p>
<blockquote>
<p>结果一：<br>main start 0s<br>Response form service 1 Value 1 496.2µs<br>main stop 496.2µs<br>结果二：<br>main start 0s<br>Response form service 2 Value 1 0s<br>main stop 0s</p>
</blockquote>
<p>在上面的程序中，两个通道在其缓冲区中都有两个值。因为我们向容量为 2 的缓冲区通道分别发送了两个值，所以这些通道发送操作不会阻塞并且会执行下面的 select 块。 select 块中的所有 case 操作都不会阻塞，因为每个通道中都有两个值，而我们的 case 操作只需要取出其中一个值。因此，go 运行时会随机选择一个 case 操作并执行其中的代码。</p>
<h4 id="2-3-8-default-case-块"><a href="#2-3-8-default-case-块" class="headerlink" title="2.3.8.default case 块"></a>2.3.8.default case 块</h4><p>像 switch 一样， select 语句也有 default case 块。default case 块 是非阻塞的，不仅如此， default case 块可以使 select 语句永不阻塞，这意味着， 任何通道的 发送 和 接收 操作 (不管是缓冲或者非缓冲) 都不会阻塞当前线程。</p>
<p>如果有 case块的通道操作是非阻塞，那么 select会执行其case 块。如果没有那么 select将默认执行 default块.</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">    <span class="string">&quot;time&quot;</span></span><br><span class="line">)</span><br><span class="line"><span class="keyword">var</span> start time.Time</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">init</span><span class="params">()</span></span> &#123;</span><br><span class="line">    start = time.Now()</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">service1</span><span class="params">(c <span class="keyword">chan</span> <span class="type">string</span>)</span></span> &#123;</span><br><span class="line">    c &lt;- <span class="string">&quot;Hello from service 1&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">service2</span><span class="params">(c <span class="keyword">chan</span> <span class="type">string</span>)</span></span> &#123;</span><br><span class="line">    c &lt;- <span class="string">&quot;Hello from service 2&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    fmt.Println(<span class="string">&quot;main start&quot;</span>, time.Since(start))</span><br><span class="line">    chan1 := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">string</span>)</span><br><span class="line">    chan2 := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">string</span>)</span><br><span class="line">    <span class="keyword">go</span> service1(chan1)</span><br><span class="line">    <span class="keyword">go</span> service2(chan2)</span><br><span class="line">    <span class="keyword">select</span> &#123;</span><br><span class="line">    <span class="keyword">case</span> res := &lt;-chan1:</span><br><span class="line">        fmt.Println(<span class="string">&quot;Response form service 1&quot;</span>, res, time.Since(start))</span><br><span class="line">    <span class="keyword">case</span> res := &lt;-chan2:</span><br><span class="line">        fmt.Println(<span class="string">&quot;Response form service 2&quot;</span>, res, time.Since(start))</span><br><span class="line">    <span class="keyword">default</span>:</span><br><span class="line">        fmt.Println(<span class="string">&quot;No Response received&quot;</span>,time.Since(start))</span><br><span class="line">    &#125;</span><br><span class="line">    fmt.Println(<span class="string">&quot;main stop &quot;</span>,time.Since(start))</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">结果：</span></span><br><span class="line"><span class="comment">main start 0s</span></span><br><span class="line"><span class="comment">No Response received 0s</span></span><br><span class="line"><span class="comment">main stop  0s</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure>
<ul>
<li>在上面的程序中，因为通道是非缓冲的，case 块的通道操作都是阻塞的，所有 default 块将被执行。</li>
<li>如果上面的 select 语句没有 default 块，select 将阻塞，没有 response 会被打印出来，知道通道变成非阻塞。</li>
<li>如果带有 default, select 将是非阻塞的，调度器将不会从主线程转而调度其他协程。</li>
<li>但是我们可以使用 time.Sleep 改变这一点。 通过这种方式，主线程将把调度权转移到其他协程，在其他协程执行完毕后，调度权从新回到主线程手里。</li>
<li>当主线程重新执行的时候，通道里面已经有值了，case 操作将不会阻塞。</li>
</ul>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">    <span class="string">&quot;time&quot;</span></span><br><span class="line">)</span><br><span class="line"><span class="keyword">var</span> start time.Time</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">init</span><span class="params">()</span></span> &#123;</span><br><span class="line">    start = time.Now()</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">service1</span><span class="params">(c <span class="keyword">chan</span> <span class="type">string</span>)</span></span> &#123;</span><br><span class="line">    fmt.Println(<span class="string">&quot;service1 start&quot;</span>)</span><br><span class="line">    c &lt;- <span class="string">&quot;Hello from service 1&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">service2</span><span class="params">(c <span class="keyword">chan</span> <span class="type">string</span>)</span></span> &#123;</span><br><span class="line">    fmt.Println(<span class="string">&quot;service2 start&quot;</span>)</span><br><span class="line">    c &lt;- <span class="string">&quot;Hello from service 2&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    fmt.Println(<span class="string">&quot;main start&quot;</span>, time.Since(start))</span><br><span class="line">    chan1 := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">string</span>)</span><br><span class="line">    chan2 := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">string</span>)</span><br><span class="line">    <span class="keyword">go</span> service1(chan1)</span><br><span class="line">    <span class="keyword">go</span> service2(chan2)</span><br><span class="line">    time.Sleep(<span class="number">3</span>*time.Second)</span><br><span class="line">    <span class="keyword">select</span> &#123;</span><br><span class="line">    <span class="keyword">case</span> res := &lt;-chan1:</span><br><span class="line">        fmt.Println(<span class="string">&quot;Response form service 1&quot;</span>, res, time.Since(start))</span><br><span class="line">    <span class="keyword">case</span> res := &lt;-chan2:</span><br><span class="line">        fmt.Println(<span class="string">&quot;Response form service 2&quot;</span>, res, time.Since(start))</span><br><span class="line">    <span class="keyword">default</span>:</span><br><span class="line">        fmt.Println(<span class="string">&quot;No Response received&quot;</span>,time.Since(start))</span><br><span class="line">    &#125;</span><br><span class="line">    fmt.Println(<span class="string">&quot;main stop &quot;</span>,time.Since(start))</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">结果不唯一。</span></span><br><span class="line"><span class="comment">main start 0s</span></span><br><span class="line"><span class="comment">service2 start</span></span><br><span class="line"><span class="comment">service1 start</span></span><br><span class="line"><span class="comment">Response form service 1 Hello from service 1 3.0006729s</span></span><br><span class="line"><span class="comment">main stop  3.0006729s</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure>
<h4 id="2-3-9-空-select"><a href="#2-3-9-空-select" class="headerlink" title="2.3.9.空 select"></a>2.3.9.空 select</h4><p>和 <code>for&#123;&#125;</code> 这样的空循环很像，空 <code>select&#123;&#125;</code> 语法也是有效的。但是有一点必须要说明。<br>我们知道 <code>select</code> 将被阻塞除非有 <code>case</code> 块没有阻塞。因为 <code>select&#123;&#125;</code> 没有 <code>case</code> 非阻塞语句，主线程将阻塞并可能会导致死锁。</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">service</span><span class="params">()</span></span> &#123;</span><br><span class="line">    fmt.Println(<span class="string">&quot;Hello from service&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    fmt.Println(<span class="string">&quot;main started&quot;</span>)</span><br><span class="line">    <span class="keyword">go</span> service()</span><br><span class="line">    <span class="keyword">select</span> &#123;&#125;</span><br><span class="line">    fmt.Println(<span class="string">&quot;main stop&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">结果</span></span><br><span class="line"><span class="comment">main started</span></span><br><span class="line"><span class="comment">Hello from service</span></span><br><span class="line"><span class="comment">fatal error: all goroutines are asleep - deadlock!</span></span><br><span class="line"><span class="comment">goroutine 1 [select (no cases)]:</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure>
<p><img src="https://img.jbzj.com/file_images/article/202111/2021110914311259.png" alt="死锁"></p>
<p>在上面的程序中我们知道 select 将阻塞 main 线程，调度器将会调度 service 这个协程。在 service 执行完毕后，调度器会再去调度其他可用的协程，但是此时已经没有可用的协程，主线程也正在阻塞，所以最后的结果就是发生死锁.</p>
<h4 id="2-3-10-Deadlock"><a href="#2-3-10-Deadlock" class="headerlink" title="2.3.10.Deadlock"></a>2.3.10.Deadlock</h4><p>default 块在通道操作阻塞的时候是非常有用的，他可以避免死锁。 同时由于 default块的非阻塞特性，Go 可以避免在其他协程阻塞的时候去调度其他协程，从而避免死锁。<br>通道的发送操作也类似，， default 可以在其他协程不能被调度的时候被执行，从而避免死锁。</p>
<h4 id="2-3-11-nil通道"><a href="#2-3-11-nil通道" class="headerlink" title="2.3.11.nil通道"></a>2.3.11.nil通道</h4><h3 id="2-4-多协程协同工作"><a href="#2-4-多协程协同工作" class="headerlink" title="2.4.多协程协同工作"></a>2.4.多协程协同工作</h3><p>写两个协程，一个用来计算数字的平方，另一个用来计算数字的立方。<br><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">square</span><span class="params">(c <span class="keyword">chan</span> <span class="type">int</span>)</span></span> &#123;</span><br><span class="line">    fmt.Println(<span class="string">&quot;[square] reading&quot;</span>)</span><br><span class="line">    num := &lt;-c</span><br><span class="line">    c &lt;- num * num</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">cube</span><span class="params">(c <span class="keyword">chan</span> <span class="type">int</span>)</span></span> &#123;</span><br><span class="line">    fmt.Println(<span class="string">&quot;[cube] reading&quot;</span>)</span><br><span class="line">    num := &lt;-c</span><br><span class="line">    c &lt;- num * num * num</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    fmt.Println(<span class="string">&quot;[main] main started&quot;</span>)</span><br><span class="line">    squareChan := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">int</span>)</span><br><span class="line">    cubeChan := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">int</span>)</span><br><span class="line">    <span class="keyword">go</span> square(squareChan)</span><br><span class="line">    <span class="keyword">go</span> cube(cubeChan)</span><br><span class="line">    testNum := <span class="number">3</span></span><br><span class="line">    fmt.Println(<span class="string">&quot;[main] send testNum to squareChan&quot;</span>)</span><br><span class="line">    squareChan &lt;- testNum</span><br><span class="line">    fmt.Println(<span class="string">&quot;[main] resuming&quot;</span>)</span><br><span class="line">    fmt.Println(<span class="string">&quot;[main] send testNum to cubeChane&quot;</span>)</span><br><span class="line">    cubeChan &lt;- testNum</span><br><span class="line">    fmt.Println(<span class="string">&quot;[main] resuming&quot;</span>)</span><br><span class="line">    fmt.Println(<span class="string">&quot;[main] reading from channels&quot;</span>)</span><br><span class="line">    squareVal,cubeVal := &lt;-squareChan, &lt;-cubeChan</span><br><span class="line">    sum := squareVal + cubeVal</span><br><span class="line">    fmt.Println(<span class="string">&quot;[main] sum of square and cube of&quot;</span>,testNum,<span class="string">&quot; is&quot;</span>,sum)</span><br><span class="line">    fmt.Println(<span class="string">&quot;[main] main stop&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">结果：</span></span><br><span class="line"><span class="comment">[main] main started</span></span><br><span class="line"><span class="comment">[main] send testNum to squareChan</span></span><br><span class="line"><span class="comment">[cube] reading</span></span><br><span class="line"><span class="comment">[square] reading</span></span><br><span class="line"><span class="comment">[main] resuming</span></span><br><span class="line"><span class="comment">[main] send testNum to cubeChane</span></span><br><span class="line"><span class="comment">[main] resuming</span></span><br><span class="line"><span class="comment">[main] reading from channels</span></span><br><span class="line"><span class="comment">[main] sum of square and cube of 3  is 36</span></span><br><span class="line"><span class="comment">[main] main stop</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure></p>
<p>流程：</p>
<p>创建两个函数 square 和 cube 作为协程运行。</p>
<p>两个函数都有一个 int 类型通道参数c，从 c 中读取数据到变量num，最后把计算的数据再写入到通道 c 中。</p>
<p>在主线程中使用 make函数创建两个 int类型通道 squareChan and cubeChan然后分别运行square和cube 协程。因为调度权还在主线程，所以执行testNumb 赋值为 3。</p>
<p>然后我们把数据放入通道 squareChan 。主线程将阻塞直到通道的数据被读取。 一旦通道的数据被读取，主线程将继续执行。</p>
<p>在主线程中我们试图从这两个通道中读取数据，此时线程可能阻塞直到有数据写入到通道。这里我们使用:=语法来接收多个通道的值。</p>
<p>一旦这些协程把数据写入到通道，主线程将阻塞。当数据被写入通道中，主线程将继续执行，最后我们计算出数字的总和并打印到控制台。</p>
<h3 id="2-5-WaitGroup"><a href="#2-5-WaitGroup" class="headerlink" title="2.5.WaitGroup"></a>2.5.WaitGroup</h3><p>有一种业务场景是你需要知道所有的协程是否已执行完成他们的任务。这个和只需要随机选择一个条件为true 的 select 不同，他需要你满足所有的条件都是 true 才可以激活主线程继续执行。 这里的条件指的是非阻塞的通道操作。</p>
<h4 id="2-5-1-简介"><a href="#2-5-1-简介" class="headerlink" title="2.5.1.简介"></a>2.5.1.简介</h4><p>WaitGroup 是一个带着计数器的结构体，这个计数器可以追踪到有多少协程创建，有多少协程完成了其工作。当计数器为 0 的时候说明所有协程都完成了其工作。</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">    <span class="string">&quot;sync&quot;</span></span><br><span class="line">    <span class="string">&quot;time&quot;</span></span><br><span class="line">)</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">service</span><span class="params">(wg *sync.WaitGroup, instance <span class="type">int</span>)</span></span> &#123;</span><br><span class="line">    time.Sleep(<span class="number">2</span> * time.Second)</span><br><span class="line">    fmt.Println(<span class="string">&quot;Service called on instance&quot;</span>,instance)</span><br><span class="line">    wg.Done() <span class="comment">//协程数-1</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    fmt.Println(<span class="string">&quot;main started&quot;</span>)</span><br><span class="line">    <span class="keyword">var</span> wg sync.WaitGroup</span><br><span class="line">    <span class="keyword">for</span> i:=<span class="number">1</span>;i&lt;= <span class="number">3</span>; i++&#123;</span><br><span class="line">        wg.Add(<span class="number">1</span>)</span><br><span class="line">        <span class="keyword">go</span> service(&amp;wg,i)</span><br><span class="line">    &#125;</span><br><span class="line">    wg.Wait()<span class="comment">//阻塞</span></span><br><span class="line">    fmt.Println(<span class="string">&quot;main stop&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">结果：(结果是不唯一的，一共有3!次可能的结果)</span></span><br><span class="line"><span class="comment">main started</span></span><br><span class="line"><span class="comment">Service called on instance 2</span></span><br><span class="line"><span class="comment">Service called on instance 1</span></span><br><span class="line"><span class="comment">Service called on instance 3</span></span><br><span class="line"><span class="comment">main stop</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure>
<p>在上面的程序中，我们创建了一个sync.WaitGroup 类型的空结构体 (带着 0 值字段) wg 。 WaitGroup 结构体有一些像 noCopy, state1 和 sema 这样的内部字段。 这个结构体也有三个公开方法： Add, Wait 和 Done.</p>
<ul>
<li>Add 方法的参数是一个变量名叫 delta 的int 类型参数，主要用来内部计数。 内部计数器默认值为 0. 它用于记录多少个协程在运行。</li>
<li>当 WaitGroup创建后，计数器值为 0，我们可以通过给 Add方法传 int类型值来增加它的数量。 记住， 当协程建立后，计数器的值不会自动递增 ，因此需要我们手动递增它。</li>
<li>Wait 方法用来阻塞当前协程。一旦计数器为 0, 协程将恢复运行。 因此，我们需要一个方法去降低计数器的值。<br>Done 方法可以降低计数器的值。他不接受任何参数，因此，它每执行一次计数器就减 1。</li>
</ul>
<p>上面的例子中，我们在创建 wg 变量后，运行了三次 for 循环，每次运行的时候我们创建一个协程并给计数器加 1。</p>
<p>这意味着现在我们有三个协程在等待运行并且 WaitGroup 的计数器值为 3。注意我们传给协程函数的是一个指针，这是因为一旦在协程内部工作完成后，我们需要通过调用Done方法去降低计数器的值。</p>
<p>如果 wg 通过值复制方式传过去， 因为传递的是一个拷贝，主线程中的 wg将不会得到修改。</p>
<p>在 for 循环执行完成后，我们通过调用 wg.Wait()去阻塞当前主线程，并把调度权让给其他协程，直到计数器值为 0 之后，主线程才会被再次调度。</p>
<p>我们在另外三个协程中通过Done方法把计数器值降为 0，此时主线程将再次被调度并开始执行之后的代码。</p>
<h4 id="2-5-2-工作池"><a href="#2-5-2-工作池" class="headerlink" title="2.5.2.工作池"></a>2.5.2.工作池</h4><p>顾名思义，一个工作池并发执行某项工作的协程集合。 在上面，我们已经用到的多个协程执行一个任务，但是他们并没有执行特定的工作，只是 sleep 了一下。 如果你向协程中传一个通道，他们可以去完成一些工作，变成一个工作池。</p>
<p>所以工作池其实就是维护了多个工作协程，这些协程的功能是可以收到任务，执行任务并返回结果。他们完成任务后我们就可以收到结果。这些协程使用相同的通道来达到自己的目的。</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">    <span class="string">&quot;time&quot;</span></span><br><span class="line">)</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">sqrWorker</span><span class="params">(tasks &lt;-<span class="keyword">chan</span> <span class="type">int</span>, results <span class="keyword">chan</span> &lt;-<span class="type">int</span>, instance <span class="type">int</span>)</span></span> &#123;</span><br><span class="line">    <span class="keyword">for</span> num := <span class="keyword">range</span> tasks &#123;</span><br><span class="line">        time.Sleep(time.Millisecond) <span class="comment">//阻塞</span></span><br><span class="line">        fmt.Printf(<span class="string">&quot;[worker %v ] Sending result by worker %v \n&quot;</span>,instance,instance)</span><br><span class="line">        results &lt;- num*num</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    fmt.Println(<span class="string">&quot;main started&quot;</span>)</span><br><span class="line">    tasks := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">int</span>,<span class="number">10</span>)</span><br><span class="line">    results := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">int</span>,<span class="number">10</span>)</span><br><span class="line">    <span class="keyword">for</span> i:=<span class="number">0</span>;i&lt;<span class="number">3</span>;i++&#123;</span><br><span class="line">        <span class="keyword">go</span> sqrWorker(tasks,results,i)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="number">5</span>; i++ &#123;</span><br><span class="line">        tasks &lt;- i*<span class="number">2</span></span><br><span class="line">    &#125;</span><br><span class="line">    fmt.Println(<span class="string">&quot;[main] write 5 tasks&quot;</span>)</span><br><span class="line">    <span class="built_in">close</span>(tasks)</span><br><span class="line">    <span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="number">5</span>; i++ &#123;</span><br><span class="line">        result := &lt;-results</span><br><span class="line">        fmt.Println(<span class="string">&quot;[main] Result&quot;</span> , i , <span class="string">&quot;:&quot;</span>, result)</span><br><span class="line">    &#125;</span><br><span class="line">    fmt.Println(<span class="string">&quot;main stop&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">//结果之一</span></span><br><span class="line"><span class="comment">[main] write 5 tasks</span></span><br><span class="line"><span class="comment">[worker 0 ] Sending result by worker 0 </span></span><br><span class="line"><span class="comment">[worker 1 ] Sending result by worker 1 </span></span><br><span class="line"><span class="comment">[worker 2 ] Sending result by worker 2 </span></span><br><span class="line"><span class="comment">[main] Result 0 : 4</span></span><br><span class="line"><span class="comment">[main] Result 1 : 16</span></span><br><span class="line"><span class="comment">[main] Result 2 : 0</span></span><br><span class="line"><span class="comment">[worker 1 ] Sending result by worker 1 </span></span><br><span class="line"><span class="comment">[main] Result 3 : 64</span></span><br><span class="line"><span class="comment">[worker 0 ] Sending result by worker 0 </span></span><br><span class="line"><span class="comment">[main] Result 4 : 36</span></span><br><span class="line"><span class="comment">main stop</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure>
<p>sqrWorker 是一个带有 tasks 通道，results 通道 和 id 三个参数的协程函数。这个协程函数的任务是把从 tasks 通道接收到的数字的平方发送到 results通道。</p>
<p>在主函数中，我们创建了两个带缓冲区，容量为 10 的通道tasks and result。因此在缓冲区被充满之前，任何操作都是非阻塞的。所以有时候设置一个大点的缓冲区是个好办法。</p>
<p>然后我们循环创建多个 sqrWorker 协程，并传入 tasks 通道， results 通道 和 id 三个参数，用来传递和获取协程执行前后的数据。</p>
<p>接着我们向 tasks 非阻塞通道放入 5 个任务数据。</p>
<p>因为我们已经向任务通道放入的数据，所以我们可以关闭它，虽然这个操作不是必须的，但是如果以后运行中出现错误的话可以防止通道 range 带来的死锁问题。</p>
<p>然后我们开启循环 5 次从 results 通道接收数据，因为目前通道缓冲区没有数据，所以通道读取操作造成主线程阻塞，调度器将调度工作池的协程，直到有数据添加到 results通道。</p>
<p>当前我们有 3 个work 协程在工作，我们使用了 sleep 操作来模拟阻塞操作，所以调度器在某一个阻塞的时候会去调用其他的 work 协程，当某个 work 协程 sleep 完成后会把计算数字的平方的结果数据放入 results 缓冲无阻塞通道。</p>
<p>当 3 个协程依次交替把 task 通道的任务都完成后，for range 循环将完成，并且因为之前我们已经关闭了任务通道，所以协程也不会发生死锁。调度器将继续返回调度主线程。</p>
<p>有时候所有的工作协程可能都在阻塞，此时调度器将去调度主线程，直到 results 通道再次为空。</p>
<p>当所有 work 协程都完成任务退出后，主线程将继续拿到调度权并打印 results 通道剩下的数据，继续之后代码的执行。</p>
<h4 id="2-5-3-Mutex"><a href="#2-5-3-Mutex" class="headerlink" title="2.5.3.Mutex"></a>2.5.3.Mutex</h4><p>互斥是 Go 中一个简单的概念。在我解释它之前，先要明白什么是竞态条件。 goroutines 都有自己的独立的调用栈，因此他们之间不分享任何数据。但是有一种情况是数据存放在堆上，并且被多个 goroutines 使用。 多个 goroutines 试图去操作一个内存区域的数据会造成意想不到的后果.</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">    <span class="string">&quot;sync&quot;</span></span><br><span class="line">)</span><br><span class="line"><span class="keyword">var</span> i <span class="type">int</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">worker</span><span class="params">(wg *sync.WaitGroup)</span></span> &#123;</span><br><span class="line">    i = i+<span class="number">1</span></span><br><span class="line">    wg.Done()</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    fmt.Println(<span class="string">&quot;main started&quot;</span>)</span><br><span class="line">    <span class="keyword">var</span> wg sync.WaitGroup</span><br><span class="line">    <span class="keyword">for</span> i:=<span class="number">0</span>;i&lt;<span class="number">1000</span>;i++&#123;</span><br><span class="line">        wg.Add(<span class="number">1</span>)</span><br><span class="line">        <span class="keyword">go</span> worker(&amp;wg)</span><br><span class="line">    &#125;</span><br><span class="line">    wg.Wait()</span><br><span class="line">    fmt.Println(<span class="string">&quot;main stop&quot;</span>,i)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">结果是不同的！！</span></span><br><span class="line"><span class="comment">main started</span></span><br><span class="line"><span class="comment">main stop 985</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure>
<p>i = i + 1 这个计算有 3 步<br>(1) 得到 i 的值<br>(2) 给 i 的值加 1<br>(3) 更新 i 的值</p>
<p>这里发生很多事情，因为go是协程，这三步里面不一定都是同时顺序执行的。有可能A是顺利执行，使得i=2，但是B是读取的是A没更新的之前的i也就是1，所以就是结果会小于等于1000的，</p>
<p>除非一个协程阻塞，否则其他协程是没有机会获得调度的。那么 i = i + 1 也没有阻塞，为什么 Go 的调度器会去调度其他协程呢？</p>
<p>在任何情况下，都不应该依赖 Go 的调度算法，而应该实现自己的逻辑来同步不同的 goroutine.</p>
<p>实现方法之一就是使用我们上面提到的互斥锁。互斥锁是一个编程概念，它保证了在同一时间只能有一个线程或者协程去操作同一个数据。当一个协程想要操作数据的时候，必须获取该数据的一个锁，操作完成后必须释放锁，如果没有获取到该数据的锁，那么就不能操作这个数据。</p>
<p>在 Go 中，互斥数据结构 ( map) 由 sync 包提供。在 Go 中，多协程去操作一个值都可能会引起竞态条件。我们需要在操作数据之前使用 mutex.Lock() 去锁定它，一旦我们完成操作，比如上面提到的 i = i + 1, 我们就可以使用 mutext.Unlock() 方法解锁。</p>
<p>如果在锁定的时候，有一个协程想要读写 i 的值，那么此协程将阻塞 直到前面的协程完成操作并解锁数据。因此在某一时刻有且仅有一个协程可以操作数据，从而避免竞态条件。记住，任何锁之间的变量在解锁之前对于其他协程都不是可用的。</p>
<p>让我们使用互斥锁修改上面的例子：</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">    <span class="string">&quot;sync&quot;</span></span><br><span class="line">)</span><br><span class="line"><span class="keyword">var</span> i <span class="type">int</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">worker</span><span class="params">(wg *sync.WaitGroup,m *sync.Mutex)</span></span> &#123;</span><br><span class="line">    m.Lock()</span><br><span class="line">    i = i+<span class="number">1</span></span><br><span class="line">    m.Unlock()</span><br><span class="line">    wg.Done()</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    fmt.Println(<span class="string">&quot;main started&quot;</span>)</span><br><span class="line">    <span class="keyword">var</span> wg sync.WaitGroup</span><br><span class="line">    <span class="keyword">var</span> m sync.Mutex</span><br><span class="line">    <span class="keyword">for</span> i:=<span class="number">0</span>;i&lt;<span class="number">1000</span>;i++&#123;</span><br><span class="line">        wg.Add(<span class="number">1</span>)</span><br><span class="line">        <span class="keyword">go</span> worker(&amp;wg,&amp;m)</span><br><span class="line">    &#125;</span><br><span class="line">    wg.Wait()</span><br><span class="line">    fmt.Println(<span class="string">&quot;main stop&quot;</span>,i)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/*结果</span></span><br><span class="line"><span class="comment">main started</span></span><br><span class="line"><span class="comment">main stop 1000</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure>
<p>在上面的程序中，我们创建了一个互斥锁变量 m，并把它的指针传递给所有已创建的协程。<br>在协程内部，当我们要开始操作 i变量的时候，我们先通过 m.Lock()获得锁，操作完成后我们使用 m.Unlock()释放锁。<br>互斥锁可以帮助我们解决竞态条件。 但首要规则是避免 goroutine 之间共享资源。<br>所以官方建议不要共享内存并发，而是通过管道通信的方式并发。</p>
<h2 id="3-结语"><a href="#3-结语" class="headerlink" title="3.结语"></a>3.结语</h2><p>后部分go并发知识是参考作者summar的go并发以及书上的知识点，非常感谢作者的翻译工作，使得我能更好的理解go的channel并发机制！链接点这里<a target="_blank" rel="noopener" href="https://learnku.com/go/t/31890">channel</a></p>
<p>随着业务的不断扩大，并发能更好的发挥服务器的性能。</p>

      
    </div>
    <div class="article-footer">
      <blockquote class="mt-2x">
  <ul class="post-copyright list-unstyled">
    <li class="post-copyright-license">
      <strong>文章作者： </strong><a href="https://hwame.top" style="color:#E541E5;"> 鴻塵</a>
    </li>
    
    <li class="post-copyright-link hidden-xs">
      <strong>本文链接：</strong>
      <a href="https://hwame.top/20220327/channel-and-goroutine-in-go.html" title="Go语言中的通道" target="_blank" rel="external" style="color:#E541E5;">https://hwame.top/20220327/channel-and-goroutine-in-go.html</a>
    </li>
    
    <li class="post-copyright-license">
      <strong>版权声明： </strong> 本博客所有文章除特别声明外，均采用《<a href="http://creativecommons.org/licenses/by/4.0/deed.zh" target="_blank" rel="external" style="color:#E541E5;"><u>CC BY 4.0 CN协议</u></a>》许可协议。转载请注明出处！
    </li>
  </ul>
</blockquote>


<div class="panel panel-default panel-badger">
  <div class="panel-body">
    <figure class="media">
      <div class="media-left">
        <a href="https://hwame.top" target="_blank" class="img-burn thumb-sm visible-lg">
          <img src="https://cdn.jsdelivr.net/gh/hwame/pics@main/avatar.jpg" class="img-rounded w-full" alt="">
        </a>
      </div>
      <div class="media-body">
        <h3 class="media-heading"><a href="https://hwame.top" target="_blank"><span class="text-dark">鴻塵</span><small class="ml-1x">Pythoner, Data Analyst</small></a></h3>
        <div>个人简介：处女座不适合做码农。</div>
      </div>
    </figure>
  </div>
</div>


    </div>
  </article>
  
    
  <section id="comments">
  	
      <div id="vcomments"></div>
    
  </section>


  
</div>

  <nav class="bar bar-footer clearfix" data-stick-bottom>
  <div class="bar-inner">
  
  <ul class="pager pull-left">
    
    
    <li class="next">
      <a href="/20220323/when-to-use-pointer-in-go.html" title="Go语言什么时候使用指针"><span>下一篇&nbsp;&nbsp;</span><i class="icon icon-angle-right" aria-hidden="true"></i></a>
    </li>
    
    
    <li class="toggle-toc">
      <a class="toggle-btn collapsed" data-toggle="collapse" href="#collapseToc" aria-expanded="false" title="文章目录" role="button">
        <span style="color:#C71585">[&nbsp;</span><span style="color:#C71585">文章目录</span>
        <i class="text-collapsed icon icon-anchor"></i>
        <i class="text-in icon icon-close"></i>
        <span style="color:#C71585">]</span>
      </a>
    </li>
    
  </ul>
  
  
  
  <div class="bar-right">
    
    <div class="share-component" data-sites="weibo,qq,wechat,facebook,twitter" data-mobile-sites="weibo,wechat,qq,qzone"></div>
    
  </div>
  </div>
</nav>
  


</main>

  <script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
<footer class="footer" itemscope itemtype="http://schema.org/WPFooter">
	
	
    <ul class="social-links">
    	
        <li><a href="https://github.com/hwame" target="_blank" title="Github" data-toggle=tooltip data-placement=top><i class="icon icon-github"></i></a></li>
        
        <li><a href="https://weibo.com/hwamei" target="_blank" title="Weibo" data-toggle=tooltip data-placement=top><i class="icon icon-weibo"></i></a></li>
        
        <li><a href="https://www.zhihu.com/people/hwame" target="_blank" title="Zhihu" data-toggle=tooltip data-placement=top><i class="icon icon-zhihu"></i></a></li>
        
        <li><a href="https://segmentfault.com/u/hwame" target="_blank" title="Segmentfault" data-toggle=tooltip data-placement=top><i class="icon icon-segmentfault"></i></a></li>
        
        <li><a href="https://gitee.com/hwame" target="_blank" title="Gitee" data-toggle=tooltip data-placement=top><i class="icon icon-gitee"></i></a></li>
        
        <li><a href="/atom.xml" target="_blank" title="Rss" data-toggle=tooltip data-placement=top><i class="icon icon-rss"></i></a></li>
        
    </ul>

    <div class="copyright">
    	
        &copy; 2025 鴻塵
        
        
        <div class="publishby" style="font-family:Courier">
            <span id="busuanzi_container_site_pv">
                <i class="icon icon-eye"></i><span id="busuanzi_value_site_pv" style="font-family:Courier"></span>&nbsp;
                <i class="icon icon-users"></i><span id="busuanzi_value_site_uv" style="font-family:Courier"></span>
            </span>
            <!--
            <br>✯<a target="_blank" rel="noopener" href="https://beian.miit.gov.cn/" style="color:#0FFFAA">鄂ICP备2020019329号</a>
            -->
            <br><i class="icon icon-clock" style="transform:rotate(90deg)"></i><span id="sitetime" style="font-family:Courier"></span>
        </div>
        
        <!--
        <div class="publishby">
        	Theme by <a href="https://github.com/cofess" target="_blank"> cofess </a>base on <a href="https://github.com/cofess/hexo-theme-pure" target="_blank">pure</a>.
        </div>
        -->
    </div>
    
</footer>
<script>
    function siteTime(){
        window.setTimeout("siteTime()", 1000);
        var seconds = 1000;
        var minutes = seconds * 60;
        var hours = minutes * 60;
        var days = hours * 24;
        var years = days * 365;
        var today = new Date();
        var todayYear = today.getFullYear();
        var todayMonth = today.getMonth()+1;
        var todayDate = today.getDate();
        var todayHour = today.getHours();
        var todayMinute = today.getMinutes();
        var todaySecond = today.getSeconds();
        /* Date.UTC() -- 返回date对象距世界标准时间(UTC)1970年1月1日午夜之间的毫秒数(时间戳)
        year - 作为date对象的年份，为4位年份值
        month - 0-11之间的整数，做为date对象的月份
        day - 1-31之间的整数，做为date对象的天数
        hours - 0(午夜24点)-23之间的整数，做为date对象的小时数
        minutes - 0-59之间的整数，做为date对象的分钟数
        seconds - 0-59之间的整数，做为date对象的秒数
        microseconds - 0-999之间的整数，做为date对象的毫秒数 */
        var t1 = Date.UTC(2020,05,19,21,20,52); //建站时间
        var t2 = Date.UTC(todayYear,todayMonth,todayDate,todayHour,todayMinute,todaySecond);
        var diff = t2-t1;
        var diffYears = Math.floor(diff/years);
        var diffDays = Math.floor((diff/days)-diffYears*365);
        var diffHours = Math.floor((diff-(diffYears*365+diffDays)*days)/hours);
        var diffMinutes = Math.floor((diff-(diffYears*365+diffDays)*days-diffHours*hours)/minutes);
        var diffSeconds = Math.floor((diff-(diffYears*365+diffDays)*days-diffHours*hours-diffMinutes*minutes)/seconds);
        // 将「数字」转「补零字符串」
        var y = diffYears.toString()
        var d = diffDays.toString().padStart(3, '0')
        var h = diffHours.toString().padStart(2, '0')
        var m = diffMinutes.toString().padStart(2, '0')
        var s = diffSeconds.toString().padStart(2, '0')
        document.getElementById("sitetime").innerHTML=y+":"+d+":"+h+":"+m+":"+s;
    }
    siteTime();
</script>
  <script src="//cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js"></script>
<script>
window.jQuery || document.write('<script src="js/jquery.min.js"><\/script>')
</script>
<div id="go-top"></div>
<style type="text/css">
#go-top {
 width:40px;height:40px;
 background-color:#DDA0DD;
 position:relative;
 border-radius:20px;
 position:fixed;right:20px;bottom:50px;
 cursor:pointer;display:none;
}
#go-top:after {
 content:" ";
 position:absolute;left:14px;top:14px;
 border-top:2px solid #fff;border-right:2px solid #fff;
 width:12px;height:12px;
 transform:rotate(-45deg);
}
#go-top:hover {
 background-color:#8A2BE2;
}
</style>
<script>
$(function () {
  var top=$("#go-top");
  $(window).scroll(function () {
    ($(window).scrollTop() > 300) ? top.show(300) : top.hide(200);
    $("#go-top").click(function () {
      $('body,html').animate({scrollTop:0});
      return false();
    })
  });
});
</script>

<script src="/js/plugin.min.js"></script>


<script src="/js/application.js"></script>


    <script>
(function (window) {
    var INSIGHT_CONFIG = {
        TRANSLATION: {
            POSTS: '文章',
            PAGES: '页面',
            CATEGORIES: '分类',
            TAGS: '标签',
            UNTITLED: '(未命名)',
        },
        ROOT_URL: '/',
        CONTENT_URL: '/content.json',
    };
    window.INSIGHT_CONFIG = INSIGHT_CONFIG;
})(window);
</script>

<script src="/js/insight.js"></script>





   




   
    

  <script src='/waline.js'></script>
  <link rel="stylesheet" type="text/css" href="/waline.css" />
  
  <script type="text/javascript">
 
  
Waline.init({
      el: '#vcomments',
      serverURL: 'https://waline.hwame.top',
});
  </script>

     


<!-- 20201211添加判断 -->

   
  <script src="//cdn.jsdelivr.net/npm/@fancyapps/fancybox@latest/dist/jquery.fancybox.min.js"></script>
  <script>
  //利用 FancyBox 实现点击图片放大
  $(document).ready(function() {
    $('article img').not('[hidden]').not('.panel-body img').each(function() {
      var $image = $(this);
      var imageCaption = $image.attr('alt');
      var $imageWrapLink = $image.parent('a');
      if ($imageWrapLink.length < 1) {
        var src = this.getAttribute('src');
        var idx = src.lastIndexOf('?');
        if (idx != -1) {
          src = src.substring(0, idx);
        }
        $imageWrapLink = $image.wrap('<a href="' + src + '"></a>').parent('a');
      }
      $imageWrapLink.attr('data-fancybox', 'images');
      if (imageCaption) {
        $imageWrapLink.attr('data-caption', imageCaption);
      }
    });
    $().fancybox({
      selector: '[data-fancybox="images"]',
      hash: false,
      loop: true,
    });
  });
  </script>







  
  <style>
    .copy-btn {
      display: inline-block;
      padding: 6px 12px;
      font-size: 13px;
      font-weight: 700;
      line-height: 20px;
      color: #333;
      white-space: nowrap;
      vertical-align: middle;
      cursor: pointer;
      background-color: #eee;
      background-image: linear-gradient(#fcfcfc, #eee);
      border: 1px solid #d5d5d5;
      border-radius: 3px;
      user-select: none;
      outline: 0;
    }

    .highlight-wrap .copy-btn {
      transition: opacity .3s ease-in-out;
      opacity: 0;
      padding: 2px 6px;
      position: absolute;
      right: 4px;
      top: 8px;
      z-index: 2;
    }

    .highlight-wrap:hover .copy-btn,
        .highlight-wrap .copy-btn:focus {
      opacity: 1
    }

    .highlight-wrap {
      position: relative;
    }
  </style>
  
  <script>
    addLoadEvent(()=>{
      $('.highlight').each(function (i, e) {
        var $wrap = $('<div>').addClass('highlight-wrap')
        $(e).after($wrap)
        $wrap.append($('<button>').addClass('copy-btn').append('一键复制').on('click', function (e) {
          var code = $(this).parent().find(".code")[0].innerText
          
          var ta = document.createElement('textarea')
          document.body.appendChild(ta)
          ta.style.position = 'absolute'
          ta.style.top = '0px'
          ta.style.left = '0px'
          ta.value = code
          ta.select()
          ta.focus()
          var result = document.execCommand('copy')
          document.body.removeChild(ta)
          
            if(result)$(this).text('复制成功')
            else $(this).text('复制失败')
          
          $(this).blur()
        })).on('mouseleave', function (e) {
          var $b = $(this).find('.copy-btn')
          setTimeout(function () {
            $b.text('一键复制')
          }, 300)
        }).append(e)
      })
    })
  </script>

<script type="text/x-mathjax-config">
    MathJax.Hub.Config({
        tex2jax: {
            inlineMath: [ ["$","$"], ["\\(","\\)"] ],
            skipTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code'],
            processEscapes: true
        }
    });
    MathJax.Hub.Queue(function() {
        var all = MathJax.Hub.getAllJax();
        for (var i = 0; i < all.length; ++i)
            all[i].SourceElement().parentNode.className += ' has-jax';
    });
</script>
<script src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
</body>
</html>